﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Microsoft.Win32;


namespace Co9320.NamaAlert.Engine
{
	public enum StatusType : byte
	{
		Local,
		Sync,
		RecentError
	}

	public static class SntpTime
	{
		private struct TimeOrigine
		{
			private Stopwatch watch_;

			public TimeOrigine(DateTime Pivot)
				:this()
			{
				watch_ = new Stopwatch();
				watch_.Start();
				this.Pivot = Pivot;
			}

			public DateTime Pivot
			{
				get;
				private set;
			}

			public TimeSpan Elapsed
			{
				get
				{
					return watch_.Elapsed;
				}
			}

			public DateTime Now
			{
				get
				{
					return Pivot + watch_.Elapsed;
				}
			}
		}

		private static Timer RoundTimer_;
		private static TimerCallback TimerCallBack_;
		private static ReaderWriterLock Lock_;

		private static TimeOrigine Originate_;

		private static List<string> HostList_;
		private static int ActiveHostIndex_;

		private static UdpClient Client_;

		private static int ErrorTryCount_;

		static SntpTime()
		{
			TimerCallBack_ = new TimerCallback(CallBack);

			RoundTimer_ = new Timer(TimerCallBack_);
			Lock_ = new ReaderWriterLock();

			HostList_ = new List<string>();

			HostList_.Add("ntp.nict.jp");
			HostList_.Add("ntp.dnsbalance.ring.gr.jp");

			ActiveHostIndex_ = 0;
			Client_ = new UdpClient(HostList_[ActiveHostIndex_], 123);
			Client_.Client.ReceiveTimeout = 5000;
			Client_.Client.SendTimeout = 5000;


			Status = StatusType.Local;

			ErrorTryCount_ = 0;

			RoundTimer_.Change(new TimeSpan(0, 0, 0), new TimeSpan(0, 5, 0));

		}


		private static void CallBack(object Param)
		{
			byte[] msg = NtpMessage.CreateRequestMessage();
			byte[] rcv;
			DateTime RcvStamp;

			try
			{
			IPEndPoint ep = null;
				Client_.Send(msg, msg.Length);
				rcv = Client_.Receive(ref ep);
				RcvStamp = DateTime.Now;
			}
			catch
			{

				Client_.Close();

				ActiveHostIndex_++;
				if (ActiveHostIndex_ >= HostList.Count)
				{
					ActiveHostIndex_ = 0;
				}


				Client_ = new UdpClient(HostList[ActiveHostIndex_], 123);

				if (++ErrorTryCount_ > 10)
				{
					RoundTimer_.Change(new TimeSpan(1, 0, 0), new TimeSpan(1, 0, 0));
				}
				else
				{
					RoundTimer_.Change(new TimeSpan(0, 0, 10), new TimeSpan(0, 60, 0));
				}

				if (LastSynchTime == DateTime.MinValue)
				{
					Status = StatusType.Local;
				}
				else
				{
					Status = StatusType.RecentError;
				}

				return;
			}

			NtpMessage dta = new NtpMessage(rcv);

			TimeSpan delay = (RcvStamp - dta.OriginateTimeStamp.ToDateTime()) -
				(dta.ReceiveTimestamp.ToDateTime() - dta.TransmitTimestamp.ToDateTime());

			delay = new TimeSpan(delay.Ticks / 2);

			Lock_.AcquireWriterLock(Timeout.Infinite);

			Originate_ = new TimeOrigine(dta.TransmitTimestamp.ToDateTime() + delay);

			Lock_.ReleaseWriterLock();

			Status = StatusType.Sync;
		}


		public static IList<string> HostList
		{
			get
			{
				return HostList_;
			}
		}



		public static StatusType Status
		{
			get;
			private set;
		}

		public static DateTime LastSynchTime
		{
			get
			{
				try
				{
					Lock_.AcquireWriterLock(Timeout.Infinite);
					return Originate_.Pivot;
				}
				finally
				{
					Lock_.ReleaseWriterLock();
				}
			}
		}


		public static DateTime Now
		{
			get
			{
				if (Status == StatusType.Local)
				{
					return DateTime.Now;
				}
				else
				{
					try
					{
						Lock_.AcquireReaderLock(Timeout.Infinite);
						return Originate_.Now;
					}
					finally
					{
						Lock_.ReleaseReaderLock();
					}
				}
			}
		}

		public static void Spin()
		{
			DateTime n = Now;
		}
	}
}
